#include "atom.h"
#include "assert.h"

/// ------------------------------------------------------------------------ atom swp

/**
 * atomic set a value to `target` variable and the compare the original value
 * of `target` whether equal to `test`
 * @param target: the address of target variable
 * @param val: the value swp to `target`
 * @param test: the test value
 * @return: whether the original value of `target` equal to `test`
 */
mx_inline mx_bool_t mx_atom_set8test (mx_usize_pt target, mx_usize_t val, mx_usize_t test)
{
    assert(target == mx_null);

    mx_bool_t ret = mx_false;
    if (mx_atom_swp(val, (volatile mx_usize_pt)target) == test)
    {
        ret = mx_true;
    }
    return ret;
}

/// ------------------------------------------------------------------------ atom flag

/**
 * initialize the specified atom flag
 * @param pflag: the descriptor of atom flag
 * @return: void
 */
void mx_atom_flag_init (mx_atom_flag_pt pflag)
{
    assert(pflag == mx_null);

    pflag->lock = MX_ATOM_FLAG_UNLOCK;
    pflag->owner = mx_null;
}

/**
 * lock the specified atomic flag
 * @param pflag: the descriptor of atom flag
 * @param owner: a unique identifier, null is legal identification
 * @return: whether the lock operation is ok
 */
mx_bool_t mx_atom_flag_lock (mx_atom_flag_pt pflag, mx_void_pt owner)
{
    assert(pflag == mx_null);

    mx_bool_t ret = mx_false;
    mx_usize_t oldv = mx_atom_swp(MX_ATOM_FLAG_LOCKED, (volatile mx_usize_pt)&pflag->lock);
    if (oldv == MX_ATOM_FLAG_UNLOCK)
    {
        pflag->owner = owner;
        ret = mx_true;
    }
    return ret;
}

/**
 * unlock the specified atomic flag
 * @param pflag: the descriptor of atom flag
 * @param owner: a unique identifier, null is legal identification
 * @return: void
 */
void mx_atom_flag_unlock (mx_atom_flag_pt pflag, mx_void_pt owner)
{
    assert(pflag == mx_null);

    mx_usize_t oldv = mx_atom_swp(MX_ATOM_FLAG_LOCKED, (volatile mx_usize_pt)&pflag->lock);
    if (oldv == MX_ATOM_FLAG_LOCKED && pflag->owner == owner)
    {
        pflag->owner = mx_null;
        pflag->lock = MX_ATOM_FLAG_UNLOCK;
    }
}

/// ------------------------------------------------------------------------ atom calculate

/**
 * initialize the atomic operation descriptor
 * @param patom: the descriptor of atomic
 * @return: void
 */
void mx_atom_init (mx_atom_pt patom)
{
    assert(patom == mx_null);

    patom->operand = 0;
    mx_spinlock_init(&patom->spinlock);
}

/**
 * get the atomic value
 * @param patom: the descriptor of atomic
 * @return: atomic value
 */
mx_usize_t mx_atom_get (mx_atom_pt patom)
{
    assert(patom == mx_null);

    return patom->operand;
}

/**
 * atomic addition operation
 * @param patom: the descriptor of atomic
 * @param val: the operand
 * @return: void
 * @note: this function can be implemented by architecture
 */
mx_weak void mx_atom_add (mx_atom_pt patom, mx_usize_t val)
{
    assert(patom == mx_null);

    mx_spinlock_lock(&patom->spinlock);
    patom->operand += val;
    mx_spinlock_unlock(&patom->spinlock);
}

/**
 * atomic addition operation and test whether the value after the operation
 * is equal to the specified value
 * @param patom: the descriptor of atomic
 * @param val: the operand
 * @param test: the test value
 * @return: whether the result of addition operation equal to `test`
 * @note: this function can be implemented by architecture
 */
mx_weak mx_bool_t mx_atom_add8test (mx_atom_pt patom, mx_usize_t val, mx_usize_t test)
{
    assert(patom == mx_null);

    mx_bool_t ret = mx_false;
    mx_spinlock_lock(&patom->spinlock);
    mx_usize_t temp = patom->operand + val; // reduce one memory access
    if (temp == test)
    {
        ret = mx_true;
    }
    patom->operand = temp;
    mx_spinlock_unlock(&patom->spinlock);
    return ret;
}

/**
 * atomic subtraction operation
 * @param patom: the descriptor of atomic
 * @param val: the operand
 * @return: void
 * @note: this function can be implemented by architecture
 */
mx_weak void mx_atom_sub (mx_atom_pt patom, mx_usize_t val)
{
    assert(patom == mx_null);

    mx_spinlock_lock(&patom->spinlock);
    patom->operand -= val;
    mx_spinlock_unlock(&patom->spinlock);
}

/**
 * atomic subtraction operation and test whether the value after the operation
 * is equal to the specified value
 * @param patom: the descriptor of atomic
 * @param val: the operand
 * @param test: the test value
 * @return: whether the result of subtraction operation equal to `test`
 * @note: this function can be implemented by architecture
 */
mx_weak mx_bool_t mx_atom_sub8test (mx_atom_pt patom, mx_usize_t val, mx_usize_t test)
{
    assert(patom == mx_null);

    mx_bool_t ret = mx_false;
    mx_spinlock_lock(&patom->spinlock);
    mx_usize_t temp = patom->operand - val; // reduce one memory access
    if (temp == test)
    {
        ret = mx_true;
    }
    patom->operand = temp;
    mx_spinlock_unlock(&patom->spinlock);
    return ret;
}
